﻿using System;
using System.Activities;
using System.Activities.Statements;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;

using Microsoft.TeamFoundation.Build.Client;
using Microsoft.TeamFoundation.Build.Workflow.Activities;
using Microsoft.TeamFoundation.Build.Workflow.Tracking;


namespace Qetza.TeamFoundation.Build.Workflow.Activities
{
    /// <summary>
    /// Activity to updates the assembly version and/or assembly file version in a group of files.
    /// </summary>
    [BuildActivity(HostEnvironmentOption.All)]
    [ActivityTracking(ActivityTrackingOption.ActivityOnly)]
    public sealed class UpdateAssemblyInfo : Activity
    {
        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="UpdateAssemblyInfo"/> class.
        /// </summary>
        public UpdateAssemblyInfo()
        {
            base.Implementation = () => this.CreateBody();
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the AssemblyInfo files to update.
        /// </summary>
        /// <value>
        /// The AssemblyInfo files to update.
        /// </value>
        [RequiredArgument]
        public InArgument<IEnumerable<string>> AssemblyInfoFiles { get; set; }

        /// <summary>
        /// Gets or sets the assembly version format.
        /// </summary>
        /// <value>
        /// The assembly version format.
        /// </value>
        [DefaultValue((string)null)]
        public InArgument<string> AssemblyVersionFormat { get; set; }

        /// <summary>
        /// Gets or sets the assembly file version format.
        /// </summary>
        /// <value>
        /// The assembly file version format.
        /// </value>
        [DefaultValue((string)null)]
        public InArgument<string> AssemblyFileVersionFormat { get; set; }

        /// <summary>
        /// Gets or sets the assembly informational version format.
        /// </summary>
        /// <value>
        /// The assembly informational version format.
        /// </value>
        [DefaultValue((string)null)]
        public InArgument<string> AssemblyInformationalVersionFormat { get; set; }

        /// <summary>
        /// Gets or sets the max updated assembly version.
        /// </summary>
        /// <value>
        /// The max updated assembly version.
        /// </value>
        public OutArgument<Version> MaxAssemblyVersion { get; set; }

        /// <summary>
        /// Gets or sets the max updated assembly file version.
        /// </summary>
        /// <value>
        /// The max updated assembly file version.
        /// </value>
        public OutArgument<Version> MaxAssemblyFileVersion { get; set; }

        /// <summary>
        /// Gets or sets the max updated assembly informational version.
        /// </summary>
        /// <value>
        /// The max updated assembly informational version.
        /// </value>
        public OutArgument<string> MaxAssemblyInformationalVersion { get; set; }

        #endregion

        #region Private Helpers

        // Creates the activity body.
        private Activity CreateBody()
        {
            // create variables
            var files = new Variable<IEnumerable<string>>();
            var file = new DelegateInArgument<string>();

            var currentAssemblyVersion = new Variable<Version>(env => new Version(0, 0, 0, 0));
            var currentAssemblyFileVersion = new Variable<Version>(env => new Version(0, 0, 0, 0));
            var currentAssemblyInformationalVersion = new Variable<string>(env => string.Empty);

            // create activity
            return new Sequence() { 
                Variables = {
                    files
                    }, 
                Activities = {
                    // assign default version values (0.0.0.0)
                    new Assign<Version>() {
                        Value = new InArgument<Version>(env => new Version(0, 0, 0, 0)),
                        To = new OutArgument<Version>(env => this.MaxAssemblyVersion.GetLocation(env).Value)
                        },
                    new Assign<Version>() {
                        Value = new InArgument<Version>(env => new Version(0, 0, 0, 0)),
                        To = new OutArgument<Version>(env => this.MaxAssemblyFileVersion.GetLocation(env).Value)
                        },
                    new Assign<string>() {
                        Value = new InArgument<string>(env => string.Empty),
                        To = new OutArgument<string>(env => this.MaxAssemblyInformationalVersion.GetLocation(env).Value)
                        },
                    // for each files
                    new If() {
                        Condition = new InArgument<bool>(env => this.AssemblyInfoFiles.Get(env).Any()), 
                        Then = new Sequence() {
                            Variables = {
                                currentAssemblyVersion,
                                currentAssemblyFileVersion,
                                currentAssemblyInformationalVersion
                                },
                            Activities = { 
                                new ForEach<string>() {
                                    Values = new InArgument<IEnumerable<string>>(env => this.AssemblyInfoFiles.Get(env)),
                                    Body = new ActivityAction<string>() {
                                        Argument = file,
                                        Handler = new Sequence() {
                                            Activities = {
                                                // update file and compute max version
                                                new UpdateAssemblyInfoFile() {
                                                    FilePath = new InArgument<string>(file),
                                                    AssemblyVersionFormat = new InArgument<string>(env => this.AssemblyVersionFormat.Get(env)),
                                                    AssemblyFileVersionFormat = new InArgument<string>(env => this.AssemblyFileVersionFormat.Get(env)),
                                                    AssemblyInformationalVersionFormat = new InArgument<string>(env => this.AssemblyInformationalVersionFormat.Get(env)),
                                                    AssemblyVersion = new OutArgument<Version>(currentAssemblyVersion),
                                                    AssemblyFileVersion = new OutArgument<Version>(currentAssemblyFileVersion),
                                                    AssemblyInformationalVersion = new OutArgument<string>(currentAssemblyInformationalVersion)
                                                    },
                                                new WriteBuildMessage() {
                                                    Importance = new InArgument<BuildMessageImportance>(BuildMessageImportance.Normal), 
                                                    Message = new InArgument<string>(env => "AssemblyInfo file '" + file.Get(env) + "' was successfully updated.")
                                                    },
                                                new If() {
                                                    Condition = new InArgument<bool>(env => currentAssemblyVersion.Get(env) > this.MaxAssemblyVersion.Get(env)),
                                                    Then = new Assign<Version>() {
                                                        Value = new InArgument<Version>(currentAssemblyVersion),
                                                        To = new OutArgument<Version>(env => this.MaxAssemblyVersion.GetLocation(env).Value)
                                                        }
                                                    },
                                                new If() {
                                                    Condition = new InArgument<bool>(env => currentAssemblyFileVersion.Get(env) > this.MaxAssemblyFileVersion.Get(env)),
                                                    Then = new Assign<Version>() {
                                                        Value = new InArgument<Version>(currentAssemblyFileVersion),
                                                        To = new OutArgument<Version>(env => this.MaxAssemblyFileVersion.GetLocation(env).Value)
                                                        }
                                                    },
                                                new If() {
                                                    Condition = new InArgument<bool>(env => currentAssemblyInformationalVersion.Get(env).CompareTo(this.MaxAssemblyInformationalVersion.Get(env)) > 0),
                                                    Then = new Assign<string>() {
                                                        Value = new InArgument<string>(currentAssemblyInformationalVersion),
                                                        To = new OutArgument<string>(env => this.MaxAssemblyInformationalVersion.GetLocation(env).Value)
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            },
                        Else = new WriteBuildWarning() { Message = new InArgument<string>("No AssemblyInfo files given.") }
                        }
                    }
                };
        }

        #endregion
    }
}
